﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using GBricks.Collections;

namespace GBricks.Tests.Collections
{
  [TestClass]
  public sealed class SequenceTests
  {
    [TestMethod]
    public void SequenceInvalidIndex() {
      IList<int> list = new int[0];
      Assert.AreEqual(Sequence.InvalidIndex, list.IndexOf(1));
    }

    [TestMethod]
    public void SequenceIsSingle() {
      var array = new int[0];
      Assert.IsFalse(array.IsSingle());
      var set = new HashSet<int>();
      Assert.IsFalse(set.IsSingle());
      Assert.IsFalse(Enumerable.Repeat(0, 0).IsSingle());
      Assert.IsTrue(Enumerable.Repeat(0, 1).IsSingle());
      Assert.IsFalse(Enumerable.Repeat(0, 2).IsSingle());
      Assert.IsFalse(Enumerable.Repeat(0, 3).IsSingle());
    }

    [TestMethod]
    public void SequenceIsMany() {
      var array = new int[0];
      Assert.IsFalse(array.IsMany());
      var set = new HashSet<int>();
      Assert.IsFalse(set.IsMany());
      Assert.IsFalse(Enumerable.Repeat(0, 0).IsMany());
      Assert.IsFalse(Enumerable.Repeat(0, 1).IsMany());
      Assert.IsTrue(Enumerable.Repeat(0, 2).IsMany());
      Assert.IsTrue(Enumerable.Repeat(0, 3).IsMany());
    }

    [TestMethod]
    public void SequenceIndexOf() {
      IEnumerable<string> items = new[] { "a", null, String.Empty, "B", "c", "D", "e", };
      Assert.AreEqual(items.IndexOf("A"), Sequence.InvalidIndex);
      Assert.AreEqual(new HashSet<string>(items).IndexOf("A"), Sequence.InvalidIndex);
      Assert.AreEqual(items.IndexOf("a"), 0);
      Assert.AreEqual(items.IndexOf("a", 1), Sequence.InvalidIndex);
      Assert.AreEqual(items.IndexOf("A", 0, StringComparer.OrdinalIgnoreCase), 0);
      Assert.AreEqual(items.IndexOf("A", 1, StringComparer.OrdinalIgnoreCase), Sequence.InvalidIndex);

      Assert.AreEqual(items.IndexOf(null), 1);
      Assert.AreEqual(items.IndexOf(String.Empty), 2);

      Assert.AreEqual(items.IndexOf(null, NullableString.Ordinal), 1);
      Assert.AreEqual(items.IndexOf("F", NullableString.Ordinal), Sequence.InvalidIndex);
      Assert.AreEqual(items.IndexOf(null, 2, NullableString.Ordinal), 2);
    }

    [TestMethod]
    public void SequenceGetEmptyArray() {
      SequenceGetEmptyArray<int>();
      SequenceGetEmptyArray(0);
      SequenceGetEmptyArray<string>();
      SequenceGetEmptyArray(String.Empty);
    }

    private void SequenceGetEmptyArray<T>(T pattern = default(T)) {
      var array1 = Sequence.GetEmptyArray<T>();
      Assert.IsNotNull(array1);

      var array2 = Sequence.GetEmptyArray(pattern);
      Assert.IsNotNull(array2);

      Assert.AreEqual(array1.Length, 0);
      Assert.AreSame(array1, array2);
    }

    [TestMethod]
    public void SequenceAsList() {
      SequenceAsList<int>();
      SequenceAsList<string>();
    }

    private void SequenceAsList<T>() {
      var items = default(IEnumerable<T>);
      var list1 = Sequence.AsList<T>(items);
      Assert.IsNotNull(list1);
      Assert.AreEqual(list1.Count, 0);

      var collection = new HashSet<T>() { default(T), };
      var list2 = Sequence.AsList<T>(collection);
      Assert.IsNotNull(list2);
      Assert.AreNotSame(list2, collection);
      Assert.AreEqual(list2.Count, collection.Count);

      var array = new T[] { default(T), default(T), };
      var list3 = Sequence.AsList<T>(array);
      Assert.IsNotNull(list3);
      Assert.AreSame(list3, array);
    }

    [TestMethod]
    public void SequenceAsReadOnly() {
      SequenceAsReadOnly<int>();
      SequenceAsReadOnly<string>();
    }

    private void SequenceAsReadOnly<T>() {
      SequenceAsReadOnly((IEnumerable<T> items) => Sequence.AsReadOnly(items));
      SequenceAsReadOnly((IEnumerable<T> items) => Sequence.AsReadOnlyCollection(items));
      SequenceAsReadOnly((IEnumerable<T> items) => Sequence.AsReadOnlyList(items));

      SequenceAsReadOnlyKeyed(1, 2, 3);
      SequenceAsReadOnlyKeyed(DateTime.Now);
      SequenceAsReadOnlyKeyed(GetType().Name);
    }

    private void SequenceAsReadOnly<T, TResult>(Func<IEnumerable<T>, TResult> selector) where TResult : IEnumerable<T> {
      SequenceAsReadOnly(selector, default(IEnumerable<T>));
      SequenceAsReadOnly(selector, new HashSet<T> { default(T), default(T), });
      SequenceAsReadOnly(selector, new List<T> { default(T), default(T), });
      SequenceAsReadOnly(selector, new T[] { default(T), default(T), });
      SequenceAsReadOnly(selector, new ReadOnlyCollection<T>(new T[] { default(T), default(T), }));
    }

    private void SequenceAsReadOnlyKeyed<T>(params T[] items) {
      var source = new TestKeyedCollection<T>(items);
      SequenceAsReadOnly<T, KeyedCollection<string, T>, ReadOnlyKeyedCollection<string, T>>(xs => Sequence.AsReadOnly(xs), source);
    }

    private void SequenceAsReadOnly<T, TResult>(Func<IEnumerable<T>, TResult> selector, IEnumerable<T> source) where TResult : IEnumerable<T> {
      SequenceAsReadOnly<T, IEnumerable<T>, TResult>(selector, source);
    }

    private void SequenceAsReadOnly<T, TSource, TResult>(Func<TSource, TResult> selector, TSource source)
      where TSource : IEnumerable<T>
      where TResult : IEnumerable<T> {
      Argument.NotNull(selector, "selector");

      var result = selector(source);
      Assert.IsNotNull(result);
      Assert.AreEqual(result.Count(), source != null ? source.Count() : 0);

      if(source != null && typeof(TResult).IsAssignableFrom(source.GetType())) {
        Assert.AreSame(result, source);
      } else {
        Assert.AreNotSame(result, source);
      }//if
    }

    [TestMethod]
    public void SequenceBreadthFirst() {
      var root = new TestNode<int>(0) {
        Children = {
          new TestNode<int>(1) { Children = { 4, 5 }, },
          new TestNode<int>(2) { Children = { 6, 7 }, },
          new TestNode<int>(3) { Children = { 8, 9 }, },
        },
      };

      var result = Sequence.BreadthFirst(root, node => node.Children).Select(item => item.Value).ToList();
      CollectionAssert.AreEqual(result, Enumerable.Range(0, 10).ToList());
    }

    [TestMethod]
    public void SequenceDepthFirst() {
      var root = new TestNode<int>(0) {
        Children = {
          new TestNode<int>(1) { Children = { 2, 3 }, },
          new TestNode<int>(4) { Children = { 5, 6 }, },
          new TestNode<int>(7) { Children = { 8, 9 }, },
        },
      };

      var result = Sequence.DepthFirst(root, node => node.Children).Select(item => item.Value).ToList();
      CollectionAssert.AreEqual(result, Enumerable.Range(0, 10).ToList());
    }

    private sealed class TestKeyedCollection<T> : KeyedCollection<string, T>
    {
      public TestKeyedCollection(params T[] items) {
        if(items != null) {
          foreach(var item in items) {
            Add(item);
          }//for
        }//if

        var key = GetKeyForItem(default(T));
        if(!Contains(key)) {
          Add(default(T));
        }//if
      }

      protected override string GetKeyForItem(T item) {
        return Convert.ToString(item, CultureInfo.InvariantCulture);
      }
    }

    private sealed class TestNode<T>
    {
      public TestNode(T value) {
        Value = value;
        Children = new List<TestNode<T>>();
      }

      public T Value { get; private set; }
      public ICollection<TestNode<T>> Children { get; private set; }

      public override string ToString() {
        return Convert.ToString(Value, CultureInfo.InvariantCulture);
      }

      public static implicit operator TestNode<T>(T value) {
        return new TestNode<T>(value);
      }
    }
  }
}
